package spring.authority;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 跨站请求伪造，校验 CSRF token，h2-console 有自己生成 token 的机制
        http.csrf(csrf -> csrf.disable())
                // 默认情况下，Spring Security 要求每个请求都要经过认证
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().authenticated())
                // 自定义 authenticationProvider，否则会验证 Spring 默认用户 user
                .authenticationProvider(authenticationProvider())
                // http 基础认证，存在密码泄露风险
                .httpBasic(httpBasic -> httpBasic.disable())
                // 表单认证，适合复杂的认证，登录页面本身不需要认证，否则会造成重定向循环
                // 在 SPA 中，前端应当由相同 url 的路由，后端会有登录 url 的匹配，但是页面不会刷新，前端的页面渲染由路由匹配完成
                .formLogin(login -> login.loginPage("/login").permitAll())
                // 允许在 frame 中展示同源 response
                .headers(header -> header.frameOptions(options -> options.sameOrigin()))
                // 注销页面不需要认证
                .logout(logout -> logout.permitAll().logoutSuccessUrl("/login"));
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> userDetailsService.loadUserByUsername(username);
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
        return authProvider;
    }

}
